RipplePulse : SampleSynthDescriptor{

	classvar <>pulseSpeedOffset = 1;
	
	createGuiControls{
		
		this.put(\speed,  	c.new(default: 10, min: 1, max:20, midiNum: 100, postLaunch: true ));
		this.put(\pulseAttack,  	c.new(default: 0.1, max:0.2, postLaunch: true ));
		this.put(\pulseDecay,  	c.new(default: 0.1, max:0.2), postLaunch: true );
		this.put(\curve,  	c.new(default: 0, min: -20, max: 20, postLaunch: true ));
		this.put(\steps,  	c.new(default: 0.1, min: 0.1, max:8) );
		this.put(\spread,  	c.new(default: 0, min: 0, max:1, postLaunch: true) );
		
		
	}
	
	
	
		
	*playSound{|args, group, buffer, busNum|
		var pulseAttack, pulseDecay, speed, r;
		
		// avoid clicks by making scaling pulse attack/decay if they're too long. 
		
		pulseAttack = ArrayUtils.getValueFromSynthArgs(args, \pulseAttack);
		pulseDecay = ArrayUtils.getValueFromSynthArgs(args, \pulseDecay);
		speed = ArrayUtils.getValueFromSynthArgs(args, \speed);
		
		if(pulseAttack + pulseDecay > (1 / speed)){
			r = 1 / (speed * (pulseAttack + pulseDecay));
			
			// not sure why, but still getting clicks at higher speeds 
			if(speed > 0.6){
				r = r * 0.95;	
			};
			
			pulseAttack = pulseAttack * r;
			pulseDecay = pulseDecay * r;
			
			ArrayUtils.setValueInSynthArgs(args, \pulseAttack, pulseAttack);
			ArrayUtils.setValueInSynthArgs(args, \pulseDecay, pulseDecay);
			
		};
		
		
		ArrayUtils.setValueInSynthArgs(args, \pulseSpeedOffset, pulseSpeedOffset);
		
		ArrayUtils.setValueInSynthArgs(args, \speed, speed);
	
		// now make sure we don't try to play past the end of the buffer
		this.prevetBufferOverRun(args, buffer);
		
		Synth(this.getSynthName, args, group);
	}
	
	
	
	
	*prevetBufferOverRun{|args, buffer|
	
		try{
		
			var maxTime, time, startTime, bufferTime, maxStartTime;
			maxTime = ArrayUtils.getValueFromSynthArgs(args,\len) + ArrayUtils.getValueFromSynthArgs(args,\attack) + ArrayUtils.getValueFromSynthArgs(args,\decay);
			time =  ArrayUtils.getValueFromSynthArgs(args,\pulseAttack) + ArrayUtils.getValueFromSynthArgs(args,\pulseDecay);
			time = time.min(maxTime);
			bufferTime = buffer.numFrames / Server.default.sampleRate;
			startTime = ( bufferTime - 0.1) * ArrayUtils.getValueFromSynthArgs(args,\startOffset);
			maxStartTime = bufferTime - time;
			startTime = startTime.min(maxStartTime);
			
			ArrayUtils.setValueInSynthArgs(args, \startOffset, (startTime / bufferTime));
		}
		
	}
	
	
	*loadSynth{
	
		
		SynthDef(
					
		this.getSynthName,
		//"LongHiPassPulse",
		{
				arg 
				len = 1, 
				startOffset = 0, 
				bufnum, 
				attack = 0.05, 
				decay = 0.05, 
				volume = 0.7,
				volume2 = 1, 
				tremeloFreq = 0, 
				tremeloDepth = 0,
				tremeloRand = 0,
				pitchRatio = 1,
				loopLength,
				pitchUp, 
				pitchDown,
				speed,
				steps = 2.5,
				pulseAttack,
				pulseDecay,
				curve,
				hiPassFreq,
				longCurve,
				busNum,
				r,
				swellGate,
				swellAmount = 1,
				pulseSpeedOffset=1, 
				spread = 0.5,
				speedLag = 3;
				
				var p, c, startFrames, endLoop, loopFrames, numChannels, maxLoopLength, amp, pan = 0, pulse;
				
				maxLoopLength = BufFrames.kr(bufnum);
				
				startFrames = maxLoopLength * startOffset; 
				
				loopFrames = loopLength * SynthConstants.getLoopResution;
				loopFrames = if( loopLength > 0.95, maxLoopLength, loopFrames );
				endLoop = (startFrames + loopFrames).min( BufFrames.kr(bufnum) );
				
				//swellEnv = EnvGen.kr(Env.linen(3,0,3, 1), swellGate) * swellAmount;
				//swellEnv = swellEnv + 1;
				
				pulseSpeedOffset = Lag.kr(pulseSpeedOffset, 0.1);
				
				speed = Lag.kr(speed, speedLag);
				
				pulse = Impulse.kr(speed * pulseSpeedOffset);
				
				p = PlayBuf.ar(
					2,
					bufnum, 
					BufRateScale.kr(bufnum) * pitchRatio * (pitchUp + 1) * (1 - pitchDown), 
					startPos: startFrames,
					trigger:pulse) 
				* EnvGen.kr(Env.perc(pulseAttack /( pulseSpeedOffset * 1.1), pulseDecay/ ( pulseSpeedOffset * 1.1), 1, curve ), gate: Impulse.kr(speed * pulseSpeedOffset), doneAction: 0)
				* (SynthSugar.stepOsc(speed * pulseSpeedOffset, steps.ceil) + 1);
				
				amp = Amplitude.kr(p, 0, 1);
				
				p = p / (0.01 + (amp * 20)); 
				
				pan = Select.kr(PulseCount.kr(pulse).wrap(0,2), [1,-1]) * spread;
				
				p = if( BufChannels.kr(bufnum) > 1, 
					(
						Balance2.ar(
							p[0], // left
							p[1], // right
							pan
						)
					),
					(
						Pan2.ar(
							p,
							pan // this is pan
						)	
					)
				  );
				  
				  
				Out.ar(busNum, 
					p
				);
				
				
			}
			
		).load(Server.default);
	
	}

}